// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package typecheck

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/types"
	"cmd/internal/src"
)

// crawlExports对导出的
// 对象（变量、函数和类型）的给定列表中的类型/对象图进行爬网。它遍历
// 类型的所有部分，并遵循定义类型的方法。任何被发现为
// 可能由导入程序直接调用或在内联后调用的函数都会标记为
// ExportInline，以便IEExport。go知道如何导出他们的内联体。
// 
// crawlExports的总体目的是避免导出无法实际引用的内联方法
// ，从而显著减少导出的大小
// 。
// 
// 对于可从全局变量访问的非泛型定义类型，我们仅为导出的方法设置
// ExportInline。对于直接命名的已定义类型或递归嵌入此类类型的
// 我们为所有方法设置ExportInline，因为
// 这些类型可以嵌入另一个本地类型。对于在可内联函数中任意位置使用的
// 实例化类型，我们在
// 基泛型类型的所有方法上设置ExportInline，因为创建任何实例化的
// 类型都需要所有方法。
func crawlExports(exports []*ir.Name) {
	p := crawler{
		marked:         make(map[*types.Type]bool),
		embedded:       make(map[*types.Type]bool),
		generic:        make(map[*types.Type]bool),
		checkFullyInst: make(map[*types.Type]bool),
	}
	for _, n := range exports {
		p.markObject(n)
	}
}

type crawler struct {
	marked         map[*types.Type]bool // 已被markType 
	embedded       map[*types.Type]bool // 已被markEmbed 
	generic        map[*types.Type]bool // 已被markGeneric 
	checkFullyInst map[*types.Type]bool // 已被checkForFullyInst 
}

// markObject访问可访问对象（函数、方法、全局类型或全局变量）
func (p *crawler) markObject(n *ir.Name) {
	if n.Op() == ir.ONAME && n.Class == ir.PFUNC {
		p.markInlBody(n)
	}

	// 如果已声明的类型名可访问，用户可以将其嵌入到自己的
	// 类型中，这使得即使是未报告的方法也可以访问。
	if n.Op() == ir.OTYPE {
		p.markEmbed(n.Type())
	}

	p.markType(n.Type())
}

// markType递归访问可从t访问的类型，以识别可能需要其
// 内联体的函数。对于实例化的泛型类型，它访问基本的
// 泛型类型，该类型具有相关的方法。
func (p *crawler) markType(t *types.Type) {
	if orig := t.OrigType(); orig != nil {
		// 转换为基本泛型类型。
		t = orig
	}
	if p.marked[t] {
		return
	}
	p.marked[t] = true

	// 如果这是已定义的类型，请标记其所有关联的
	// 方法。跳过接口类型，因为t.方法只包含其未扩展的方法集（即，不包括接口嵌入的
	// 方法集），
	// 下面的switch语句将处理其完整的方法集。
	if t.Sym() != nil && t.Kind() != types.TINTER {
		for _, m := range t.Methods().Slice() {
			if types.IsExported(m.Sym.Name) {
				p.markObject(m.Nname.(*ir.Name))
			}
		}
	}

	// 递归地标记给定类型t的
	// 值时可以生成的任何类型：取消引用指针；索引或
	// 在数组、切片或映射上迭代；从
	// /频道接收；访问结构字段或接口方法；或者
	// 调用函数。
	// 
	// 值得注意的是，我们没有标记函数参数类型，因为
	// 用户已经需要某种方法来构造
	// 这些类型的值。
	switch t.Kind() {
	case types.TPTR, types.TARRAY, types.TSLICE:
		p.markType(t.Elem())

	case types.TCHAN:
		if t.ChanDir().CanRecv() {
			p.markType(t.Elem())
		}

	case types.TMAP:
		p.markType(t.Key())
		p.markType(t.Elem())

	case types.TSTRUCT:
		if t.IsFuncArgStruct() {
			break
		}
		for _, f := range t.FieldSlice() {
			// 如果是完全实例化的类型，请标记未报告字段的类型，因为我们创建并实例化了
			// 在导入过程中看到的任何完全实例化类型的方法
			// 请参阅typecheck.substInstType的结尾。
			if types.IsExported(f.Sym.Name) || f.Embedded != 0 ||
				isPtrFullyInstantiated(f.Type) {
				p.markType(f.Type)
			}
		}

	case types.TFUNC:
		for _, f := range t.Results().FieldSlice() {
			p.markType(f.Type)
		}

	case types.TINTER:
		for _, f := range t.AllMethods().Slice() {
			if types.IsExported(f.Sym.Name) {
				p.markType(f.Type)
			}
		}

	case types.TTYPEPARAM:
		// 无需遵循其他类型。
	}
}

// markEmbed类似于markType，但处理需要重新导出的查找方法，因为t可以嵌入到用户代码
// （可能是传递的）。
func (p *crawler) markEmbed(t *types.Type) {
	if t.IsPtr() {
		// 定义的指针类型；无论如何都不允许嵌入。
		if t.Sym() != nil {
			return
		}
		t = t.Elem()
	}

	if orig := t.OrigType(); orig != nil {
		// 转换为基本泛型类型。
		t = orig
	}

	if p.embedded[t] {
		return
	}
	p.embedded[t] = true

	// 如果t是已定义的类型，则重新导出其所有方法。与markType中的
	// 不同，我们在这里甚至包括未报告的方法，因为我们
	// 仍然需要为它们生成包装，即使用户不能直接引用它们。
	if t.Sym() != nil && t.Kind() != types.TINTER {
		for _, m := range t.Methods().Slice() {
			p.markObject(m.Nname.(*ir.Name))
		}
	}

	// 如果t是一个结构，则递归访问其嵌入字段。
	if t.IsStruct() {
		for _, f := range t.FieldSlice() {
			if f.Embedded != 0 {
				p.markEmbed(f.Type)
			}
		}
	}
}

// markGeneric接受实例化类型或基泛型类型t，并标记基泛型类型t的所有
// 方法。如果为
// 导出编写了基泛型类型，即使没有显式标记为导出，则其所有方法都需要
// 可用于实例化，因为我们总是创建指定
// 实例化类型的所有方法。非导出的方法通常必须实例化，因为它们可能被导出的方法或同一包中的其他泛型函数调用。
func (p *crawler) markGeneric(t *types.Type) {
	if t.IsPtr() {
		t = t.Elem()
	}
	if orig := t.OrigType(); orig != nil {
		// 转换为基本泛型类型。
		t = orig
	}
	if p.generic[t] {
		return
	}
	p.generic[t] = true

	if t.Sym() != nil && t.Kind() != types.TINTER {
		for _, m := range t.Methods().Slice() {
			p.markObject(m.Nname.(*ir.Name))
		}
	}
}

// CheckForFullInST在类型中查找完全实例化的类型（在任何嵌套
// 级别）。如果找到一个完全实例化的类型，它将确保导出必要的
// dictionary和shape方法。它更新了p.checkFullyInst，因此
// 只遍历每个特定类型一次。
func (p *crawler) checkForFullyInst(t *types.Type) {
	if p.checkFullyInst[t] {
		return
	}
	p.checkFullyInst[t] = true

	if t.IsFullyInstantiated() && !t.HasShape() && !t.IsInterface() && t.Methods().Len() > 0 {
		// 对于任何完全实例化的类型，相关的
		// 字典和形状实例化将已经创建或在导入数据中。
		// 确保它们已导出，这样任何
		// 其他内联此函数的包都将有
		// 它们可供导入，因此在内联后将不需要
		// 另一轮方法和字典
		// 实例化。
		baseType := t.OrigType()
		shapes := make([]*types.Type, len(t.RParams()))
		for i, t1 := range t.RParams() {
			shapes[i] = Shapify(t1, i, baseType.RParams()[i])
		}
		for j := range t.Methods().Slice() {
			baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name)
			dictsym := MakeDictSym(baseNname.Sym(), t.RParams(), true)
			if dictsym.Def == nil {
				in := Resolve(ir.NewIdent(src.NoXPos, dictsym))
				dictsym = in.Sym()
			}
			Export(dictsym.Def.(*ir.Name))
			methsym := MakeFuncInstSym(baseNname.Sym(), shapes, false, true)
			if methsym.Def == nil {
				in := Resolve(ir.NewIdent(src.NoXPos, methsym))
				methsym = in.Sym()
			}
			methNode := methsym.Def.(*ir.Name)
			Export(methNode)
			if HaveInlineBody(methNode.Func) {
				// 如果
				// 实例化是内联的，那么也导出主体。
				ImportedBody(methNode.Func)
				methNode.Func.SetExportInline(true)
			}
		}
	}

	// 进入类型。即使它是一个完全实例化的类型，我们也会下降，因为实例化的类型可能在它的内部有其他实例化的类型（在字段、方法等中）。
	switch t.Kind() {
	case types.TPTR, types.TARRAY, types.TSLICE:
		p.checkForFullyInst(t.Elem())

	case types.TCHAN:
		p.checkForFullyInst(t.Elem())

	case types.TMAP:
		p.checkForFullyInst(t.Key())
		p.checkForFullyInst(t.Elem())

	case types.TSTRUCT:
		if t.IsFuncArgStruct() {
			break
		}
		for _, f := range t.FieldSlice() {
			p.checkForFullyInst(f.Type)
		}

	case types.TFUNC:
		if recv := t.Recv(); recv != nil {
			p.checkForFullyInst(t.Recv().Type)
		}
		for _, f := range t.Params().FieldSlice() {
			p.checkForFullyInst(f.Type)
		}
		for _, f := range t.Results().FieldSlice() {
			p.checkForFullyInst(f.Type)
		}

	case types.TINTER:
		for _, f := range t.AllMethods().Slice() {
			p.checkForFullyInst(f.Type)
		}
	}
}

// markInlBody将n的内联体标记为导出，递归地
// 确保所有调用的函数也被标记。
func (p *crawler) markInlBody(n *ir.Name) {
	if n == nil {
		return
	}
	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
		base.Fatalf("markInlBody: unexpected %v, %v, %v", n, n.Op(), n.Class)
	}
	fn := n.Func
	if fn == nil {
		base.Fatalf("markInlBody: missing Func on %v", n)
	}
	if !HaveInlineBody(fn) {
		return
	}

	if fn.ExportInline() {
		return
	}
	fn.SetExportInline(true)

	ImportedBody(fn)

	var doFlood func(n ir.Node)
	doFlood = func(n ir.Node) {
		t := n.Type()
		if t != nil {
			if t.HasTParam() {
				// 如果使用了任何泛型类型，请确保导出该泛型类型的方法，并扫描其他可能的导出。
				p.markGeneric(t)
			} else {
				p.checkForFullyInst(t)
			}
			if base.Debug.Unified == 0 {
				// 如果未导出类型的方法被提升并可由
				// 嵌入到导出类型中，则会使该类型可访问。
				// 
				// 示例：
				// 
				// type t struct{}
				// func（t）M（）{}
				// 
				// func F（）接口{return struct{t}}{。M、 并内联调用
				// 到“struct{t}”。M、 这样就可以联系到“t.M”。
				if t.IsStruct() {
					for _, f := range t.FieldSlice() {
						if f.Embedded != 0 {
							p.markEmbed(f.Type)
						}
					}
				}
			}
		}

		switch n.Op() {
		case ir.OMETHEXPR, ir.ODOTMETH:
			p.markInlBody(ir.MethodExprName(n))
		case ir.ONAME:
			n := n.(*ir.Name)
			switch n.Class {
			case ir.PFUNC:
				p.markInlBody(n)
				// 注意：这个导出（）和下面的导出似乎是多余的，
				// 因为在
				// 导出的函数体中遇到的任何函数/外部名称都将通过iexport中的qualifiedent（）自动导出
				// 。去
				Export(n)
			case ir.PEXTERN:
				Export(n)
			}
		case ir.OMETHVALUE:
			// 好的，因为我们还没有内联对方法值的间接调用。
		case ir.OCLOSURE:
			// VisitList不访问闭包体，因此在闭包体上强制对VisitList进行递归调用。
			ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood)
		}
	}

	// 递归标识
	// 重新导出的所有引用函数。我们甚至希望包含未调用的函数，
	// 因为内联后它们可能是可调用的。
	ir.VisitList(fn.Inl.Body, doFlood)
}

// 如果t是完全实例化的类型，或是指向完全实例化类型的
// 指针，则isPtrFullyInstantiated返回true。
func isPtrFullyInstantiated(t *types.Type) bool {
	return t.IsPtr() && t.Elem().IsFullyInstantiated() ||
		t.IsFullyInstantiated()
}
